/**
 * MegaMek - Copyright (C) 2000,2001,2002,2003,2004 Ben Mazur (bmazur@sev.org)
 * 
 *  This program is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU General Public License as published by the Free 
 *  Software Foundation; either version 2 of the License, or (at your option) 
 *  any later version.
 * 
 *  This program is distributed in the hope that it will be useful, but 
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 *  for more details.
 */

package megamek.common.actions;

import java.util.Enumeration;

import megamek.common.AmmoType;
import megamek.common.BattleArmor;
import megamek.common.Entity;
import megamek.common.Infantry;
import megamek.common.IGame;
import megamek.common.Mech;
import megamek.common.Mounted;
import megamek.common.PlanetaryConditions;
import megamek.common.Targetable;
import megamek.common.ToHitData;
import megamek.common.WeaponType;

// TODO: Auto-generated Javadoc
/**
 * Abstract superclass for any action where an entity is attacking another
 * entity.
 */
public abstract class AbstractAttackAction extends AbstractEntityAction
implements AttackAction {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -897197664652217134L;

	/** The target type. */
	private int targetType;

	/** The target id. */
	private int targetId;

	// default to attacking an entity, since this is what most of them are
	/**
	 * Instantiates a new abstract attack action.
	 *
	 * @param entityId the entity id
	 * @param targetId the target id
	 */
	public AbstractAttackAction(int entityId, int targetId) {
		super(entityId);
		this.targetType = Targetable.TYPE_ENTITY;
		this.targetId = targetId;
	}

	/**
	 * Instantiates a new abstract attack action.
	 *
	 * @param entityId the entity id
	 * @param targetType the target type
	 * @param targetId the target id
	 */
	public AbstractAttackAction(int entityId, int targetType, int targetId) {
		super(entityId);
		this.targetType = targetType;
		this.targetId = targetId;
	}

	/* (non-Javadoc)
	 * @see megamek.common.actions.AttackAction#getTargetType()
	 */
	public int getTargetType() {
		return targetType;
	}

	/* (non-Javadoc)
	 * @see megamek.common.actions.AttackAction#getTargetId()
	 */
	public int getTargetId() {
		return targetId;
	}

	/* (non-Javadoc)
	 * @see megamek.common.actions.AttackAction#setTargetType(int)
	 */
	public void setTargetType(int targetType) {
		this.targetType = targetType;
	}

	/* (non-Javadoc)
	 * @see megamek.common.actions.AttackAction#setTargetId(int)
	 */
	public void setTargetId(int targetId) {
		this.targetId = targetId;
	}

	/**
	 * Gets the target.
	 *
	 * @param iGame the g
	 * @return the target
	 */
	public Targetable getTarget(IGame iGame) {
		return iGame.getTarget(getTargetType(), getTargetId());
	}

	/**
	 * Gets the entity.
	 *
	 * @param iGame the g
	 * @return the entity
	 */
	public Entity getEntity(IGame iGame) {
		Entity entity = iGame.getEntity(getEntityId());
		// if we have an artyattack, we might need to get an out-of-game entity
		// if it died or fled
		if (entity == null) {
			entity = iGame.getOutOfGameEntity(getEntityId());
		}
		return entity;
	}

	/**
	 * used by the toHit of derived classes atype may be null if not using an
	 * ammo based weapon.
	 *
	 * @param game the game
	 * @param target the target
	 * @param atype the atype
	 * @param attacker the attacker
	 * @param isWeapon the is weapon
	 * @return the to hit data
	 */
	public static ToHitData nightModifiers(IGame game, Targetable target,
			AmmoType atype, Entity attacker, boolean isWeapon) {
		ToHitData toHit = null;

		Entity entity = null;
		if (target.getTargetType() == Targetable.TYPE_ENTITY) {
			entity = (Entity) target;
		}
		toHit = new ToHitData();

		int lightCond = game.getPlanetaryConditions().getLight();
		if(lightCond == PlanetaryConditions.L_DAY) {
			//not nighttime so just return
					return toHit;
		}

		// The base night penalty
		int nightModifier = 0;   
		nightModifier = game.getPlanetaryConditions().getLightHitPenalty(isWeapon);
		toHit.addModifier(nightModifier, game.getPlanetaryConditions().getLightCurrentName());

		boolean illuminated = false;
		if (entity != null) {
			illuminated = entity.isIlluminated();
			// hack for unresolved actions so client shows right BTH
			if (!illuminated) {
				illuminated = setIlluminated(game, entity, illuminated);
			}
		}
		// Searchlights reduce the penalty to zero (or 1 for pitch-black) (except for dusk/dawn)
		int searchlightMod = Math.min(3, nightModifier);
		if(entity != null && lightCond > PlanetaryConditions.L_DUSK) {
			nightModifier = assignNightModifier(toHit, entity,
					nightModifier, illuminated, searchlightMod);
		}else if (game.isPositionIlluminated(target.getPosition())) {    // So do flares
			toHit.addModifier(-nightModifier,
					"target illuminated by flare");
			nightModifier = 0;
		}else if (atype != null) { // Certain ammunitions reduce the penalty
			nightModifier = changeNightModifier(atype, toHit, nightModifier);
		}
		// Laser heatsinks
		lookLaserHeatSink(game, toHit, entity, nightModifier);


		//now check for general hit bonuses for heat
		checkHitBonusHeat(game, attacker, toHit, entity);

		return toHit;
	}

	/**
	 * @param game
	 * @param toHit
	 * @param entity
	 * @param nightModifier1
	 */
	public static void lookLaserHeatSink(IGame game, ToHitData toHit,
			Entity entity, int nightModifier1) {
		int nightModifier = nightModifier1;
		if (nightModifier > 0 && entity != null && entity instanceof Mech
				&& ((Mech) entity).hasLaserHeatSinks()) {
			boolean lhsused = false;
			if (entity.heat > 0) {
				toHit.addModifier(-nightModifier,
						"target overheated with laser heatsinks");
				nightModifier = 0;
			}
			// actions that generate heat give a -1 modifier
			else if (entity.heatBuildup > 0 || entity.isStealthActive()) {
				lhsused = true;
			} else {
				// Unfortunately, we can't just check weapons fired by the
				// target because isUsedThisRound() is not valid if the attacker
				// declared first.
				// therefore, enumerate WeaponAttackActions...
				for (Enumeration<EntityAction> enumActions = game.getActions(); enumActions
						.hasMoreElements();) {
					EntityAction entityAction = enumActions.nextElement();
					lhsused = targetHeatWeapon(entity, lhsused,
							entityAction);
				}
			}

			if (lhsused) {
				toHit.addModifier(-1, "target uses laser heatsinks");
			}
		}
	}

	/**
	 * @param game
	 * @param attacker
	 * @param toHit
	 * @param entity
	 */
	public static void checkHitBonusHeat(IGame game, Entity attacker,
			ToHitData toHit, Entity entity) {
		if(entity != null && !(attacker instanceof Infantry && !(attacker instanceof BattleArmor))) {                 
			int heatBonus = game.getPlanetaryConditions().getLightHeatBonus(entity.heat);
			if(heatBonus < 0) {
				toHit.addModifier(heatBonus, "target excess heat at night");
			}
		}
	}

	/**
	 * @param toHit
	 * @param entity
	 * @param nightModifier1
	 * @param illuminated
	 * @param searchlightMod
	 * @return
	 */
	public static int assignNightModifier(ToHitData toHit, Entity entity,
			int nightModifier1, boolean illuminated, int searchlightMod) {
		int nightModifier = nightModifier1;
		if (entity.isUsingSpotlight()) {
			toHit.addModifier(-searchlightMod, "target using searchlight");
			nightModifier = nightModifier - searchlightMod;
		} else if (illuminated) {
			toHit.addModifier(-searchlightMod,"target illuminated by searchlight");
			nightModifier = nightModifier - searchlightMod;
		}
		return nightModifier;
	}

	/**
	 * @param game
	 * @param entity
	 * @param illuminated1
	 * @return
	 */
	public static boolean setIlluminated(IGame game, Entity entity,
			boolean illuminated1) {
		boolean illuminated = illuminated1;
		for (Enumeration<EntityAction> enumActions = game.getActions(); enumActions
				.hasMoreElements();) {
			EntityAction entityAction = enumActions.nextElement();
			if (entityAction instanceof SearchlightAttackAction) {
				SearchlightAttackAction saa = (SearchlightAttackAction) entityAction;
				if (saa.willIlluminate(game, entity)) {
					illuminated = true;
					break;
				}
			}
		}
		return illuminated;
	}

	/**
	 * @param entity
	 * @param lhsUsed
	 * @param entityAction
	 * @return
	 */
	public static boolean targetHeatWeapon(Entity entity, boolean lhsUsed1,
			EntityAction entityAction) {
		boolean lhsUsed = lhsUsed1;
		if (entityAction instanceof WeaponAttackAction) {
			WeaponAttackAction wAA = (WeaponAttackAction) entityAction;
			if (wAA.getEntityId() == entity.getId()) {
				Mounted weapon = entity.getEquipment(wAA
						.getWeaponId());
				WeaponType wType = (WeaponType) weapon
						.getType();
				if (wType.getHeat() != 0
						|| weapon.isRapidfire()) {
					// target fired a weapon that generates heat
					lhsUsed = true;
					//break;
				}
			}
		}
		return lhsUsed;
	}

	/**
	 * @param atype
	 * @param toHit
	 * @param nightModifier1
	 * @return
	 */
	public static int changeNightModifier(AmmoType atype, ToHitData toHit,
			int nightModifier1) {
		int nightModifier = nightModifier1;
		if ((atype.getAmmoType() == AmmoType.T_AC || atype
				.getAmmoType() == AmmoType.T_LAC)
				&& (atype.getMunitionType() == AmmoType.M_INCENDIARY_AC || atype
				.getMunitionType() == AmmoType.M_TRACER)) {
			toHit.addModifier(-1, "incendiary/tracer ammo");
			nightModifier--;
		}
		return nightModifier;
	}

}
